
#include "base64.h"
#include <stdlib.h>

static char base64_map[] = {
'A','B','C','D','E','F','G','H',
'I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X',
'Y','Z','a','b','c','d','e','f',

'g','h','i','j','k','l','m','n',
'o','p','q','r','s','t','u','v',
'w','x','y','z','0','1','2','3',
'4','5','6','7','8','9','+','/'
};

// Plain Byte: a b c  3Byte
// CB1: a 6
// CB2: a 2 b 4
// CB3: b 4 c 2
// CB4: c 6
#define GetEnByte1(bp, b1) ( (b1) = (base64_map[ (  bp[0] >> 2 )  ] ) )
#define GetEnByte2(bp, b2) ( (b2) = (base64_map[ ( (bp[0] & 0x03) << 4) | ( bp[1] >> 4) ]) )
#define GetEnByte3(bp, b3) ( (b3) = (base64_map[ ( (bp[1] & 0x0F) << 2) | ( bp[2] >> 6) ]) )
#define GetEnByte4(bp, b4) ( (b4) = (base64_map[ (  bp[2] & 0x3F) ] ) )

char * base64_encode(const unsigned char *str, int len, char *cipher) {



    int mod = len % 3;
    int cnt = len / 3;
    
    int tot_chars = ( len + 2) / 3 * 4 + 1;
    
    if ( NULL == cipher ) {
        cipher = (char *) malloc( tot_chars * sizeof( char ) ); 
    }

    //printf("tot_chars: %d\n", tot_chars);

    int p;
    int i;
    int j;
    const unsigned char *buf;

    for (p = i = j = 0;i < cnt; i++,p += 3) {
        buf = str + p;
        
        GetEnByte1( buf, cipher[j++]);
        GetEnByte2( buf, cipher[j++]);
        GetEnByte3( buf, cipher[j++]);
        GetEnByte4( buf, cipher[j++]);
        
        //     cipher[j++] = base64_map[ ( buf[0] >> 2   ) ];
        //     cipher[j++] = base64_map[ ( buf[0] & 0x03 ) << 4 | ( buf[1] >> 4 ) ];
        //     cipher[j++] = base64_map[ ( buf[1] & 0x0F ) << 2 | ( buf[2] >> 6 ) ];
        //     cipher[j++] = base64_map[ ( buf[2] & 0x3F ) ];  
    }
    

    if ( mod ) {
        buf = str + p;
        //cipher[j++] = base64_map[ ( buf[0] >> 2 ) ];
        GetEnByte1( buf, cipher[j++]);

        if ( 1 == mod ) {
            cipher[j++] = base64_map[ ( buf[0] & 0x03 ) << 4 ];
            cipher[j++] = '=';
        }
        else {
            //cipher[j++] = base64_map[ ( ( buf[0] & 0x03 ) << 4 ) | ( buf[1] >> 4) ];
            GetEnByte2( buf, cipher[j++]);
            cipher[j++] = base64_map[ (   buf[1] & 0x0F ) << 2 ];
        }
        cipher[j++] = '=';
    }
    cipher[j] = '\0';

 //   printf("cipher_len: %d\n", j);

 //   for (i = 0;i < 8;i++)
 //       printf("0x%02X ", cipher[i] );
 //   printf("cipher text: %s\n", cipher);

    return cipher;
}

// Cipher Byte: a b c d
// PB1: a 6 b 2
// PB2: b 4 c 4
// PB3: c 2 d 6


#define GetDeByte1(bp, b1)  ( (b1) = ((bp[0] << 2) | (bp[1] >> 4)) )
#define GetDeByte2(bp, b2)  ( (b2) = ((bp[1] << 4) | (bp[2] >> 2)) )
#define GetDeByte3(bp, b3)  ( (b3) = ((bp[2] << 6) | (bp[3]     )) )

static inline char get_key_of(char c) {
    for (int i = 0;i < 64;i++) {
        if (base64_map[i] == c)
            return i;
    }
    return 64;
}
unsigned char * base64_decode(const char *str, int *plen, unsigned char *plain) {

    int len = strlen( str );
    int plain_len = len / 4 * 3;
    if (NULL == plain) {
        plain = ( unsigned char * ) malloc( plain_len * sizeof( unsigned char ) );
    }
    int cnt = len / 4;

    int p;
    int i;
    int j;
    const  char *buf;


    for ( i = j = p = 0; i < cnt; i++, p += 4) {
        buf = str + p;
        char cb[4];

        int k;
        for (k = 0; k < 4; k++) {
            if (buf[k] == '=') {
                break;
            }
            cb[k] = get_key_of( buf[k] );
        }

        if ( 4 == k) {
            GetDeByte1(cb, plain[j++]);
            GetDeByte2(cb, plain[j++]);
            GetDeByte3(cb, plain[j++]); 
        }
        else{
            if ( 2 == k) {
                GetDeByte1( cb, plain[j++]);
            }
            else {
                GetDeByte1( cb, plain[j++]);
                GetDeByte2( cb, plain[j++]);
            }
        }
    }

    *plen = j;

    return plain;
}